Skip to content

Create dynamic proxies in the same ALC as the interface they implement#1247

Merged
AArnott merged 5 commits intov2.22from
dev/andarno/fixDynamicAssemblies
Aug 19, 2025
Merged

Create dynamic proxies in the same ALC as the interface they implement#1247
AArnott merged 5 commits intov2.22from
dev/andarno/fixDynamicAssemblies

Conversation

@AArnott
Copy link
Member

@AArnott AArnott commented Aug 14, 2025

This pull request introduces significant improvements to how dynamic proxies are generated and loaded in .NET environments with multiple AssemblyLoadContexts (ALCs). The main focus is to ensure that StreamJsonRpc generates proxies in the correct ALC, preventing type resolution issues when interfaces are loaded from different contexts. The changes also add new tests and documentation to validate and explain this behavior.

This change fixes two bugs:

Create dynamic proxies in fewer dynamic assemblies

This corrects a bug that deviated from the original design. The idea was for all proxies to be generated into as few dynamic assemblies as possible. We have to allow for distinct dynamic assemblies for growing sets of skip visibility check attributes, but other than that we should reuse dynamic assemblies.
The bug here was that we were not supplying a structural AssemblyName equality comparer to our ImmutableHashSet. Since AssemblyName.Equals is a reference equality check and the CLR does not de-dupe assembly names, we must supply our own equality comparison in order to get the intended behavior.

Generating proxies into the right ALC

This fixes the problem that we were always creating in the 'contextual' ALC, but not filing them as associated with that ALC. As a result, multiple ALCs in a process might share a DynamicAssembly, leading to type load failures or type equivalency failures.

To solve this, we are careful to never share dynamic assemblies across ALCs, and we document how callers can intentionally direct which ALC a dynamic proxy should be emitted into.

We now default to creating dynamic assemblies in the ALC that loaded the first interface rather than into the ALC that loaded StreamJsonRpc.

devdiv-2551415

This corrects a bug that deviated from the original design. The idea was for all proxies to be generated into as few dynamic assemblies as possible. We have to allow for distinct dynamic assemblies for growing sets of skip visibility check attributes, but other than that we should reuse dynamic assemblies.
The bug here was that we were not supplying a structural `AssemblyName` equality comparer to our `ImmutableHashSet`. Since `AssemblyName.Equals` is a reference equality check and the CLR does not de-dupe assembly names, we must supply our own equality comparison in order to get the intended behavior.
This fixes the problem that we were always creating in the 'contextual' ALC, but not filing them as associated with that ALC. As a result, multiple ALCs in a process might share a DynamicAssembly, leading to type load failures or type equivalency failures.

To solve this, we are careful to never share dynamic assemblies across ALCs, and we document how callers can intentionally direct which ALC a dynamic proxy should be emitted into.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR addresses critical issues with dynamic proxy generation in .NET environments using multiple AssemblyLoadContexts (ALCs), ensuring proxies are generated in the correct ALC to prevent type resolution failures.

  • Fixes dynamic assembly reuse by implementing proper AssemblyName equality comparison
  • Ensures dynamic proxies are generated in the appropriate AssemblyLoadContext to prevent type resolution issues
  • Adds comprehensive tests to validate ALC-specific proxy generation behavior

Reviewed Changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/UnreachableAssembly/UnreachableAssembly.csproj Creates test assembly project that will be unreachable from default ALC
test/UnreachableAssembly/SomeUnreachableClass.cs Defines test class for ALC isolation testing
test/StreamJsonRpc.Tests/UnreachableAssemblyTools.cs Provides utilities for ALC testing scenarios
test/StreamJsonRpc.Tests/StreamJsonRpc.Tests.csproj Configures test project to place UnreachableAssembly in hidden directory
test/StreamJsonRpc.Tests/JsonRpcProxyGenerationTests.cs Adds tests for dynamic assembly reuse and ALC-specific proxy generation
src/StreamJsonRpc/SkipClrVisibilityChecks.cs Fixes ImmutableHashSet creation and removes duplicate AssemblyNameEqualityComparer
src/StreamJsonRpc/ProxyGeneration.cs Implements ALC-aware dynamic assembly creation and proper assembly name comparison
src/StreamJsonRpc/AssemblyNameEqualityComparer.cs Extracts AssemblyNameEqualityComparer into separate file for reuse
docfx/docs/dynamicproxy.md Documents ALC considerations and usage patterns for dynamic proxies
StreamJsonRpc.sln Adds UnreachableAssembly project to solution

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.

@AArnott AArnott force-pushed the dev/andarno/fixDynamicAssemblies branch from 17b9e6f to 5a32ad3 Compare August 14, 2025 04:03
@AArnott AArnott merged commit 6b2434e into v2.22 Aug 19, 2025
5 of 6 checks passed
@AArnott AArnott deleted the dev/andarno/fixDynamicAssemblies branch August 19, 2025 20:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants